developmentor - a developer services company				  
				      Essential .NET Security
   Modules

Impersonation

Estimated time for completion: 30 minutes


Goals of this exercise:

  • Get some hands-on experience with impersonation
  • See some examples of dumb code leading to security holes

Overview

Pick a partner for this lab, preferably someone sitting close to you. You'll work with your partner using a named pipes client and server that uses network authentication (which is built in to Microsoft's named pipes implementation) coupled with impersonation to access files on the server using the client's credentials.


part 1- Examining the client code

The client is quite simple, it simply connects to the named pipe server and sends the name of a file it wants to read, and then spits the resulting output back to the console.

steps:

  1. Open up the pipes solution, and note that there are three projects: a client, server, and a helper class written in Managed C++ that simplifies the use of named pipes from managed code.

  2. Drill into the client project and open up the source code (pipeClient.cs). Note that it's already been provided for you, and that there is no SSPI specific code here. Named pipes, along with COM and many other networking protocols built into Windows, has SSPI support baked into it already, which is convenient. The only drawback is that the resulting connection you get won't be MAC protected or encrypted unless you turn on SMB signing (an option in security policy) and/or run the network over IPSEC (another option in security policy).


part 2- Configuring the server

Here you'll create a directory for the server to look for files requested by the client, and populate it with some data. You'll also add a daemon account for the server to run under.

steps:

  1. Open up the computer management console as an admin and add a user account called PipeServer. Set its password to something you can remember and uncheck the box "User must change password at next logon". This is the account under which your server will run.

  2. Your server will need to impersonate its clients occasionally, so grant the PipeServer account the right to "Impersonate a client after authentication". You can do this by running gpedit.msc, the group policy editor, with admin rights. Drill into Computer Configuration, Windows Settings, Security Settings, Local Policies, User Rights Assignment, and add your local PipeServer account to the list of accounts granted this privilege.

  3. Create a directory like "c:\repository". This will be your repository.

  4. Set the DACL on this new directory in the following way. Open up the Advanced DACL editor by bringing up properties on the directory, choosing the Security tab (this shows you a summary of the DACL) and pressing the "Advanced" button. Uncheck the checkbox that says "Allow inheritable permissions from the parent to propagate to this object and all child objects. Include these with entries explicitly defined here." NOTE: when you do this, you'll be asked whether you want to Copy or Remove the inherited ACEs. Select Remove so you will start with a clean slate. Now add an entry that grants the local Administrators group full control to "This folder, subfolders and files".

    This step will simplify management of the directory, as child files will automatically start with a very restricted DACL.

  5. Create a file in this directory called "discount.txt". Use notepad or the text editor of your choice to put the text "15%" in the file. This file represents a configuration type file that the server will need to access while processing a client's request, so add a permission grant that allows PipeServer to read the file.

  6. Create a few more files in your repository, granting your partner read access to some, but not all of them. The point is to make sure that your clients can only get to certain files, and that the administrator can control this policy by adjusting the DACLs on the server files.


part 3- Completing the implementation of the server

Here you'll write the code it takes to impersonate and stream requested files back to the client.

steps:

  1. Open up the server source file, pipeServer.cs. Note that a basic named pipes server has been provided for you, using the NamedPipe wrapper class. Pay special attention to what the code does after it calls WaitForConnection: once a connection is established, the client principal is immediately recorded on the thread via Thread.CurrentPrincipal. This is an example of plumbing that tucks the client information away for application code to use later.

  2. The application code can be found in the handleRequest function. Let's print out some diagnostic information to the server's console. Use Console.WriteLine to print out whether or not the client is authenticated, and what the client's name is. Since the code that called handleRequest already set Thread.CurrentPrincipal for you, this should be quite an easy task.

  3. Your next task is to open the discount.txt file in the server's security context and let the client know what discount they are receiving today. Here's the code you can use to open the file - it's your job to make sure the file system uses the server's credentials for the access check, as opposed to the client's credentials:

    using (StreamReader discountReader = new StreamReader(new FileStream(@"c:\repository\discount.txt", FileMode.Open, FileAccess.Read, FileShare.Read))) {
    	Console.WriteLine("Successfully opened the discount file.");
    	w.WriteLine("Your discount today is {0}", discountReader.ReadToEnd());
    	w.Flush();
    }
    
  4. The next bit of code is provided for you. It reads some input from the client, which is the name of the file the client wishes you to stream back to her. It prints out some diagnostic info, then there is a TODO indicating that you need to write some code to open the file and stream it back to the client. It's your job to make sure the file system uses the client's credentials for the access check, as opposed to the server's credentials. This will keep the client from reading any files she isn't supposed to read (based on the ACLs the administrator sets up on the files). Here's some code for opening the file and streaming it back to the client:

    using (StreamReader fileReader = new StreamReader(new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read))) {
    	Console.WriteLine("Successfully opened the client's requested file.");
    	w.WriteLine("Here is the file you requested:");
    	w.WriteLine(fileReader.ReadToEnd());
    	w.Flush();
    }
    

part 4- Using declarative role-based security

Here you'll use PrincipalPermission to experiment with declarative role-based security.

steps:

  1. Create a class with two methods. The following will suffice:

    class Foo {
    	public static void methodA() {
    		Console.WriteLine("methodA was called");
    	}
    	public static void methodB() {
    		Console.WriteLine("methodB was called");
    	}
    }
    
  2. Add a PrincipalPermission to the class that restricts it such that only authenticated users may use the class.

  3. Add a PrincipalPermission to the class that restricts one of the methods so that it may only be called by members of the local Administrators group. Note that you must fully qualify the name of the group: "BUILTIN\Administrators".

  4. Add calls to these two methods at the end of the handleRequest function. Here's an example of what you might write for each method:

    try {
    	Foo.methodA();
    }
    catch (Exception x) {
    	Console.WriteLine("methodA threw an exception: {0}", x.Message);
    }
    
  5. Make sure your server project builds ok, then move on to the next part.


part 5- Running the client

Time to experiment!

steps:

  1. To simulate your server running as a service under its daemon account, use the runas utility to start a command prompt using the PipeServer credentials:

    runas /u:PipeServer cmd
    
  2. From this new command prompt, launch your server. This will allow you to run in a simulated production environment, but you'll be able to see diagnostic output as it's written by the server.

  3. Now run the client, asking for the various files your partner put in their repository. Work with your partner to ensure that you are able to see are only the ones the server expected you to be able to see. Also have your partner verify that the PrincipalPermissions he setup in his server code are working as expected.

  4. To demonstrate a gaping security hole in our pipe server implementation, note the code that checks for a file called "passwords.txt". Clearly this is not a file we want the client to have access to, but the server code "protecting" this file is completely inadequate. Create a text file with this name in your server's repository, and try to use the client to see that "protected" file. HINT: the exploit is to use alternate names for the file, such as "passwords.txt." or "passwo~1.txt". To learn how to avoid these sorts of bugs, read the chapter on Canonicalization in Michael Howard's book, Writing Secure Code. We'll talk about these errors and more later in the week.

  5. To demonstrate another hole in the server, try downloading a copy of a file that's not even in the repository by adding a relative path, for example:

    pipeClient myPartnersMachine ..\..\..\..\..\..\..\labs\default.htm
    

    The point of this exercise is that all the security technology in the world can't save you if you don't write your code carefully. This is an example of why security experts say "Security is a process, not a product."

Solutions: